#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:zhengxin
@file: 01_ndarray.py
@time: 2022/10/9  10:39
# @describe: NumPy的 ndarray：一种多维数组对象

NumPy最重要的一个特点就是其N维数组对象（即ndarray），该对象是一个快速而灵活的大数据集容器。
你可以利用这种数组对整块数据执行一些数学运算，其语法跟标量元素之间的运算一样。

"""
import numpy as np


# 生成一个包含随机数据的小数组
data = np.random.randn(2, 3)
print(data)

# 数学运算
print(data * 10)
print(data + data)


# shape: 表示各维度大小的元组; dtype: 用于说明数组数据类型的对象
print(data.shape, data.dtype)


# 创建ndarray
data1 = [6, 7.5, 8, 0, 1]
arr1 = np.array(data1)
print(arr1)

# 嵌套序列（比如由一组等长列表组成的列表）将会被转换为一个多维数组：
data2 = [[1, 2, 3, 4], [5, 6, 7, 8]]
arr2 = np.array(data2)
print(arr2)

# 因为data2是列表的列表，NumPy数组arr2的两个维度的shape是从data2引入的。可以用属性ndim和shape验证：
print(arr2.ndim, arr2.shape)


# 除np.array之外，还有一些函数也可以新建数组。比如，zeros和ones分别可以创建指定长度或形状的全0或全1数组。
# empty可以创建一个没有任何具体值的数组。要用这些方法创建多维数组，只需传入一个表示形状的元组即可：
print(np.zeros(10))
print(np.zeros((3, 6)))
print(np.empty((2, 3, 2)))


# arange是Python内置函数range的数组版：
print(np.arange(15))


# ndarray的数据类型: dtype（数据类型）是一个特殊的对象，它含有ndarray将一块内存解释为特定数据类型所需的信息：
arr1 = np.array([1, 2, 3], dtype=np.float64)
arr2 = np.array([1, 2, 3], dtype=np.int32)
print(arr1.dtype, arr2.dtype)


# 你可以通过ndarray的astype方法明确地将一个数组从一个dtype转换成另一个dtype：
arr = np.array([1, 2, 3, 4, 5])
print(arr.dtype)
float_arr = arr.astype(np.float64)
print(float_arr.dtype)

# 你还可以用简洁的类型代码来表示dtype：
empty_uint32 = np.empty(8, dtype='u4')
print(empty_uint32)


""" NumPy数组的运算 """
# 数组很重要，因为它使你不用编写循环即可对数据执行批量运算。NumPy用户称其为矢量化（vectorization）。大小相等的数组之间的任何算术运算都会将运算应用到元素级：
arr = np.array([[1., 2., 3.], [4., 5., 6.]])
print(arr, arr*arr, arr-arr)

# 数组与标量的算术运算会将标量值传播到各个元素：
print(1/arr, arr**0.5)

# 大小相同的数组之间的比较会生成布尔值数组：
arr2 = np.array([[0., 4., 1.], [7., 2., 12.]])
print(arr2, arr2 > arr)



""" 基本的索引和切片 """
arr = np.arange(10)
print(arr, arr[5], arr[5:8])

# 当你将一个标量值赋值给一个切片时（如arr[5:8]=12），该值会自动传播（也就说后面将会讲到的“广播”）到整个选区。
# 跟列表最重要的区别在于，数组切片是原始数组的视图。这意味着数据不会被复制，视图上的任何修改都会直接反映到源数组上。
arr_slice = arr[5:8]
print(arr_slice, arr_slice[1])
# 现在，当我修稿arr_slice中的值，变动也会体现在原始数组arr中：
arr_slice[1] = 12345
print(arr)

# 切片[ : ]会给数组中的所有值赋值：
arr_slice[:] = 64
print(arr_slice, arr)

# 对于高维度数组，能做的事情更多。在一个二维数组中，各索引位置上的元素不再是标量而是一维数组：
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2d[2])

# 因此，可以对各个元素进行递归访问，但这样需要做的事情有点多。你可以传入一个以逗号隔开的索引列表来选取单个元素。也就是说，下面两种方式是等价的：
print(arr2d[0][2], arr2d[0, 2])


# 在多维数组中，如果省略了后面的索引，则返回对象会是一个维度低一点的ndarray（它含有高一级维度上的所有数据）。
# 因此，在2×2×3数组arr3d中：
arr3d = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
print(arr3d)

# arr3d[0]是一个2×3数组：
print(arr3d[0])

# 标量值和数组都可以被赋值给arr3d[0]：
old_values = arr3d[0].copy()
arr3d[0] = 42
print(arr3d)
arr3d[0] = old_values
print(arr3d)

# 相似的，arr3d[1,0]可以访问索引以(1,0)开头的那些值（以一维数组的形式返回）：
print(arr3d[1, 0], arr3d[1, 1])
# 虽然是用两步进行索引的，表达式是相同的：
x = arr3d[1]
print(x, x[0], x[1])



"""" 切片索引 """
# ndarray的切片语法跟Python列表这样的一维对象差不多：
print(arr)
print(arr[1:6])

# 对于之前的二维数组arr2d，其切片方式稍显不同：
print(arr2d, '*******')
print(arr2d[:2])

# 你可以一次传入多个切片，就像传入多个索引那样：
print(arr2d[:2, 1:])

# 例如，我可以选取第二行的前两列：
print(arr2d[1, :2])

# 相似的，还可以选择第三列的前两行：
print(arr2d)
print(arr2d[:2, 2])

# 注意，“只有冒号”表示选取整个轴，因此你可以像下面这样只对高维轴进行切片：
# [[2 3] [5 6] [8 9]]
print(arr2d[:, 1:])
# [[1] [4] [7]]
print(arr2d[:, :1])

# 对切片表达式的赋值操作也会被扩散到整个选区
arr2d[:2, 1] = 0
print(arr2d)




""" 布尔型索引 """
names = np.array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'])
data = np.random.randn(7, 4)
print(names)
print(data)

# 假设每个名字都对应data数组中的一行，而我们想要选出对应于名字”Bob”的所有行。跟算术运算一样，数组的比较运算（如==）也是矢量化的。因此，对names和字符串”Bob”的比较运算将会产生一个布尔型数组：
print(names == 'Bob')

# 这个布尔型数组可用于数组索引：
print(data)
print(data[names == 'Bob'])

# 选取了names == 'Bob'的行，并索引了列
print(data[names == 'Bob', 2:])

# 要选择除”Bob”以外的其他值，既可以使用不等于符号（!=），也可以通过~对条件进行否定：
print(names != 'Bob')
print(data[~(names == 'Bob')])

# 选取这三个名字中的两个需要组合应用多个布尔条件，使用&（和）、|（或）之类的布尔算术运算符即可：
mask = (names == 'Bob') | (names == 'Will')
print(mask)
print(data[mask])

# 通过布尔型数组设置值是一种经常用到的手段。为了将data中的所有负值都设置为0，我们只需：
data[data < 0] = 0
print(data)

# 通过一维布尔数组设置整行或列的值也很简单：
data[names != 'Joe'] = 7
print(data)



""" 花式索引 """
# 花式索引（Fancy indexing）是一个NumPy术语，它指的是利用整数数组进行索引。假设我们有一个8×4数组：
arr = np.empty((8, 4))
for i in range(8):
    arr[i] = i
print(arr)


# 为了以特定顺序选取行子集，只需传入一个用于指定顺序的整数列表或ndarray即可：
print(arr[[4, 3, 0, 6]])

# 使用负数索引将会从末尾开始选取行：
print(arr[[-3, -5, -7]])

# 一次传入多个索引数组会有一点特别。它返回的是一个一维数组，其中的元素对应各个索引元组：
arr = np.arange(32).reshape((8, 4))
print(arr)
# 最终选出的是元素(1,0)、(5,3)、(7,1)和(2,2)
print(arr[[1, 5, 7, 2], [0, 3, 1, 2]])
print(arr[[1, 5, 7, 2]][:, [0, 3, 1, 2]])





""" 数组转置和轴对换 """
# 转置是重塑的一种特殊形式，它返回的是源数据的视图（不会进行任何复制操作）。数组不仅有transpose方法，还有一个特殊的T属性：
arr = np.arange(15).reshape((3, 5))
print(arr)
print(arr.T)

# 在进行矩阵计算时，经常需要用到该操作，比如利用np.dot计算矩阵内积：
arr = np.random.randn(6, 3)
print(arr)
print(np.dot(arr.T, arr))

# 对于高维数组，transpose需要得到一个由轴编号组成的元组才能对这些轴进行转置（比较费脑子）：
arr = np.arange(16).reshape((2, 2, 4))
print(arr)
print(arr.transpose((1, 0, 2)))


# 简单的转置可以使用.T，它其实就是进行轴对换而已。ndarray还有一个swapaxes方法，它需要接受一对轴编号：
print(arr)
print(arr.swapaxes(1, 2))
